#include "StdAfx.h"
#include "MaterialTextEdit.h"


namespace Lit
{
	namespace Editor
	{	
		namespace Material
		{	
			TextEdit::SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent)
				: QSyntaxHighlighter(parent)
			{
				Lexer::MaterialKeywords mat;
				Lexer::Keywords::HighlightingRuleList rule( mat.getHighlightingRules() );
			}

			void TextEdit::SyntaxHighlighter::highlightBlock(const QString &text)
			{
				for( auto i=mRules.begin(); i!=mRules.end(); i++ )
				{
					const Lexer::Keywords::HighlightingRule& rule(*i);
					const QRegExp& expression(rule.pattern);
					int index = expression.indexIn(text);
					while (index >= 0) {
						int length = expression.matchedLength();
						setFormat(index, length, Police::textFormatFromPolice(rule.format));
						index = expression.indexIn(text, index + length);
					}
				}
				setCurrentBlockState(0);

				int startIndex = 0;
				if (previousBlockState() != 1)
					startIndex = Lexer::MaterialKeywords::commentStartExpression.indexIn(text);

				while (startIndex >= 0) {
					int endIndex = Lexer::MaterialKeywords::commentEndExpression.indexIn(text, startIndex);
					int commentLength;
					if (endIndex == -1) {
						setCurrentBlockState(1);
						commentLength = text.length() - startIndex;
					} else {
						commentLength = endIndex - startIndex
										+ Lexer::MaterialKeywords::commentEndExpression.matchedLength();
					}
					setFormat(startIndex, commentLength, Police::textFormatFromPolice(Lexer::MaterialKeywords::multiLineCommentFormat.format));
					startIndex = Lexer::MaterialKeywords::commentStartExpression.indexIn(text, startIndex + commentLength);
				}
			}
			TextEdit::Margin::Margin(TextEdit *edt) : QWidget(edt) {
				mEditor = edt;
			}
			TextEdit::Margin::~Margin(){}

			QSize TextEdit::Margin::sizeHint() const {
				return QSize(mEditor->lineNumberAreaWidth(), 0);
			}

			void TextEdit::Margin::paintEvent(QPaintEvent *event) {
				mEditor->lineNumberAreaPaintEvent(event);
			}
			TextEdit::TextEdit( QWidget * parent ) 
				: QPlainTextEdit( parent )
			{
				mMargin = new Margin(this);
				mSyntaxer = new SyntaxHighlighter(document());

				connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
				connect(this, SIGNAL(updateRequest(const QRect &, int)), this, SLOT(updateLineNumberArea(const QRect &, int)));
				connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));

				updateLineNumberAreaWidth(0);
				highlightCurrentLine();

				setFont( Police( FontFamily::CourrierNew, 10 ).font );
				setWordWrapMode( QTextOption::NoWrap );
				setTabStopWidth( 30 );

			}
			TextEdit::~TextEdit( ){}
			
			void TextEdit::keyPressEvent(QKeyEvent* evt)
			{
				if ( evt->key() == Qt::Key_Backtab || 
					evt->key() == Qt::Key_Tab ) {
					onTabKey(evt);
				}
				else if (evt->key() == Qt::Key_Return) {
					onReturnKey(evt);
				}
				else if (evt->key() == Qt::Key_Backspace) {
					onBackspaceKey(evt);
				}
				else {
					QPlainTextEdit::keyPressEvent(evt);
				}
			}

			/**
			 * On 'tab' key pressed.
			 * (1) Translate 'tab' into 4 spaces if there's no selection.
			 * (2) Otherwise, indent/unindent the whole selection. (SHIFT+TAB = unindent)
			 */
			void TextEdit::onTabKey(QKeyEvent* evt)
			{
				QTextCursor orig(textCursor());
				/*QTextCursor sel(orig);
				if (!sel.hasSelection()) {
					sel.select(QTextCursor::SelectionType::LineUnderCursor);
					setTextCursor( sel );
				}*/
				// block indent/unindent
				if (evt->modifiers().testFlag(Qt::ShiftModifier) ||
					evt->key() == Qt::Key_Backtab ) {
					unIndentSelection( );
				}
				else {
					indentSelection( );
				}
			}

			/**
			 * On 'return' key pressed.
			 * (1) If there's no spaces in the beggining of current line, call inherited behavior.
			 * (2) Otherwise, make proper indentation automatically.
			 */
			void TextEdit::onReturnKey(QKeyEvent* evt)
			{
				QRegExp spacesPattern("^ *");
				QTextCursor cursor = textCursor();
				cursor.select( QTextCursor::LineUnderCursor );

				QString blockText = cursor.selection().toPlainText();
				int pos = -1;
				if ( (pos=spacesPattern.indexIn(blockText)) > -1) {
					int indent = spacesPattern.matchedLength();
					// add 1 extra indent if current line begins a code block
					bool inCodeBlock = false;
					if (QRegExp("\\{").indexIn(blockText) > -1) {
						indent += 4;
						inCodeBlock = true;
					}
					QString spaces(indent, ' ');
					cursor.beginEditBlock();
					cursor.insertBlock();
					cursor.insertText(spaces);
					// add code block end "}" if needed
					if (inCodeBlock) {
						if (QRegExp("\\}").indexIn(textCursor().block().next().text()) == -1) {
							cursor.insertBlock();
							spaces = QString(indent - 4, ' ');
							spaces += "}";
							cursor.insertText(spaces);
							cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor);
							cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
						}
					}
					cursor.endEditBlock();
					setTextCursor(cursor);
				}
				else {
					QPlainTextEdit::keyPressEvent(evt);
				}
			}

			/**
			 * On 'backspace' key pressed.
			 * (1) If previous char is not space, call inherited.
			 * (2) Otherwise, unindent properly (4 spaces).
			 */
			void TextEdit::onBackspaceKey(QKeyEvent* evt)
			{
				QTextCursor cursor = textCursor();
				int oldPos = cursor.position();
				if (document()->characterAt(oldPos - 1) == ' ') {
					cursor.beginEditBlock();
					// determine selection to delete
					int newPos = oldPos - 4;
					cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
					int startPosOfLine = cursor.position();
					if (newPos < startPosOfLine) {
						newPos = startPosOfLine;
					}
					// make selection
					cursor.setPosition(oldPos, QTextCursor::MoveAnchor);
					cursor.setPosition(newPos, QTextCursor::KeepAnchor);
					cursor.deleteChar();
					cursor.endEditBlock();
					setTextCursor(cursor);
				}
				else {
					QPlainTextEdit::keyPressEvent(evt);
				}
			}
			void TextEdit::indentSelection()
			{
				QString indents("    ");
				QTextCursor cursor = textCursor();
				cursor.beginEditBlock();

				// If ther is no text selected...
				if (cursor.selection().isEmpty()) {
					//cursor.movePosition(QTextCursor::StartOfLine);
					cursor.insertText(indents);
					cursor.setPosition( cursor.position() );
					cursor.setPosition( cursor.position(), QTextCursor::KeepAnchor );
				} else { // If the selection is not empty...
					//cursor.beginEditBlock();

					// Save selection start and end
					int start = cursor.selectionStart();
					int end = cursor.selectionEnd();
					//cursor.clearSelection();

					// Set end to the end of line of the selected line
					cursor.setPosition(end);
					cursor.movePosition(QTextCursor::EndOfLine);
					end = cursor.position();

					// Set cursor to the start of the first selected line
					cursor.setPosition(start);
					cursor.movePosition(QTextCursor::StartOfLine);
					start = cursor.position();

					// While still in the selection, add "    " to the start of each line
					do {
						cursor.movePosition(QTextCursor::StartOfLine);
						cursor.insertText(indents);
						end += indents.count();
						cursor.movePosition(QTextCursor::EndOfLine);
					} while (cursor.position() < end && cursor.movePosition(QTextCursor::Down));

					// Select the changed areatabLenght
					cursor.clearSelection();
					cursor.setPosition(start);
					while (cursor.position() < end)
						cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
				}
				// Set the cursor in the GUI
				setTextCursor(cursor);
				cursor.endEditBlock();
			}

			/**
				* Unindents selection.
				* todo: implementation
				*/
			void TextEdit::unIndentSelection()
			{
				QString indents("    ");
				QString indentstab("\t");
				QTextCursor curs(textCursor());
				curs.beginEditBlock();
				bool unsel=false;
				if( !curs.hasSelection() )
				{
					curs.movePosition( QTextCursor::StartOfLine );
					curs.movePosition( QTextCursor::EndOfLine, QTextCursor::KeepAnchor );
					unsel = true;
				}
				int start = curs.selectionStart();
				int end = curs.selectionEnd();
				while( start > 0 )
				{
					curs.setPosition( start-1, QTextCursor::MoveAnchor );
					curs.setPosition( start, QTextCursor::KeepAnchor );
					QString c(curs.selection().toPlainText());
					if( c == "\n" )	break;
					start -= 1;
				}
				curs.setPosition( start );
				curs.setPosition( end, QTextCursor::KeepAnchor );

				QString sel(curs.selection().toPlainText());
				QStringList lst(sel.contains("\r\n",Qt::CaseInsensitive)?sel.split("\r\n"):sel.split("\n"));
				for( auto i=lst.begin(); i!=lst.end(); i++ )
				{
					QString str( *i );
					if( str.contains(indents, Qt::CaseInsensitive) )
					{
						str = str.remove( str.indexOf( indents ), indents.count() );
					}
					else if( str.contains(indentstab, Qt::CaseInsensitive) )
					{
						str = str.remove( str.indexOf( indentstab ), indentstab.count() );
					}
					*i = str;
					OutputDebugStringA( FROM_QT(str+"\r\n").c_str() );
				}
				curs.removeSelectedText();
				QString t(lst.join("\n"));
				end = start + t.size();
				curs.insertText( t );
				if( unsel )
				{
					end = start;
				}
				curs.setPosition( start );
				curs.setPosition( end, QTextCursor::KeepAnchor );
				setTextCursor( curs );
				curs.endEditBlock();
			}
			int TextEdit::lineNumberAreaWidth()
			{
				int digits = 1;
				int max = qMax(1, blockCount());
				while (max >= 10) {
					max /= 10;
					++digits;
				}

				int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits;

				return space;
			}
			void TextEdit::updateLineNumberAreaWidth(int /* newBlockCount */)
			{
				setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
			}
			void TextEdit::updateLineNumberArea(const QRect &rect, int dy)
			{
				if (dy)
					mMargin->scroll(0, dy);
				else
					mMargin->update(0, rect.y(), mMargin->width(), rect.height());

				if (rect.contains(viewport()->rect()))
					updateLineNumberAreaWidth(0);
			}
			void TextEdit::resizeEvent(QResizeEvent *e)
			{
				QPlainTextEdit::resizeEvent(e);

				QRect cr = contentsRect();
				mMargin->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
			}
			void TextEdit::highlightCurrentLine()
			{
				QList<QTextEdit::ExtraSelection> extraSelections;

				if (!isReadOnly()) {
					QTextEdit::ExtraSelection selection;

					QColor lineColor = QColor(Qt::yellow).lighter(160);

					selection.format.setBackground(lineColor);
					selection.format.setProperty(QTextFormat::FullWidthSelection, true);
					selection.cursor = textCursor();
					selection.cursor.clearSelection();
					extraSelections.append(selection);
				}

				setExtraSelections(extraSelections);
			}
			void TextEdit::lineNumberAreaPaintEvent(QPaintEvent *event)
			{
				QPainter painter(mMargin);
				painter.fillRect(event->rect(), Qt::lightGray);

				QTextBlock block = firstVisibleBlock(); 
				int blockNumber = block.blockNumber();
				int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
				int bottom = top + (int) blockBoundingRect(block).height();

				while (block.isValid() && top <= event->rect().bottom()) {
					if (block.isVisible() && bottom >= event->rect().top()) {
						QString number = QString::number(blockNumber + 1);
						painter.setPen(Qt::black);
						painter.drawText(0, top, mMargin->width(), fontMetrics().height(),
										Qt::AlignRight, number);
					}

					block = block.next();
					top = bottom;
					bottom = top + (int) blockBoundingRect(block).height();
					++blockNumber;
				}
			}
		};
	};
};